/*
Copyright (c) 2009, Luke Berndt
All rights reserved.
 
 * More Information: http://www.tagflo.com

Redistribution and use in source and binary forms, with or without modification, are permitted 
provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions 
  and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions 
  and the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of RoboTastic Software nor the names of its contributors may be used to endorse or 
  promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/


using System;
using System.Linq;
using System.Diagnostics;
using System.Collections.Generic;
using System.Collections;
using System.Collections.ObjectModel;
using System.Text;
using System.Data.OleDb;
using System.Data.SqlClient;
using System.Data;
using System.IO;
using System.Threading;
using System.ComponentModel;
using System.Windows.Media;
using System.Windows;
using System.Windows.Media.Imaging;

namespace TagFlo
{
    class MetaDataWorkItem
    {
        int _id;
        string _metaData;
        object _value;

        public MetaDataWorkItem(int id, string m, object v)
        {
            _id = id;
            _metaData = m;
            _value = v;
        }

        public int ID
        {
            get
            {
                return _id;
            }
        }

        public string MetaData
        {
            get
            {
                return _metaData;
            }
        }
        public object Value
        {
            get
            {
                return _value;
            }
        }


    }

    class MetaDataWorker 
    {

        private delegate void NoArgDelegate();

        public event EventHandler TagsChanged;

        protected virtual void OnTagsChanged(EventArgs e)
        {
            if (TagsChanged != null)
                TagsChanged(this, e);
        }

        
        // ContinuousCollection<Photo> photoCCollection;
        // ContinuousCollection<Tag> tagCCollection;


        ThreadedObservableCollection<Photo> photoCollection;
        ThreadedObservableCollection<Tag> tagCollection;
        PhotoDBEntities photoEntities;


        private uint paddingAmount = 2048; // 2Kb padding for this example, but really this can be any value. 
        // Our recommendation is to keep this between 1Kb and 5Kb as most metadata updates are not large.
        //DataSet dataSet;

        Queue<MetaDataWorkItem> work;
        MainWindow parent;



        private void ChangeDBValue(Photo photo, Photo dbPhoto, String element, Object value)
        {

            if (photo == null)
            {
                Trace.WriteLine("Error, wrong number of rows found!");
                return;
            }

            switch (element)
            {
                case "RemoveTag":
                    {
                        string[] changeTags;
                        string[] stringSeparators = new string[] { ", " };


                        changeTags = (value as String).Split(stringSeparators, StringSplitOptions.RemoveEmptyEntries);

                        foreach (string s in changeTags)
                        {
                            Tag tag = photo.Tags.Where(t => t.TagName == s).FirstOrDefault();    //("it.TagName = '" + s + "'").First();//photoEntities.Tags.Where("it.TagName = '" + s + "'").First();
                            if (tag != null)
                            {
                                photo.Tags.Remove(tag);
                                tag = photoEntities.Tags.Where("it.TagName = '" + s + "'").FirstOrDefault();
                                dbPhoto.Tags.Remove(tag);
                            }

                        }
                        photoEntities.SaveChanges();
                        OnTagsChanged(EventArgs.Empty); 
                    }
                    break;

                case "Tag":
                    {
                        string[] changeTags;
                        string[] stringSeparators = new string[] { ", " };


                        changeTags = (value as String).Split(stringSeparators, StringSplitOptions.RemoveEmptyEntries);

                        foreach (string s in changeTags)
                        {
                            if (!photo.Tags.Any(t => t.TagName == s))
                            {
                                Tag dbTag = photoEntities.Tags.Where("it.TagName = '" + s + "'").FirstOrDefault();

                                if (dbTag == null)
                                {
                                    dbTag = Tag.New();// new Tag();
                                    dbTag.TagName = s;
                                    dbTag.TagType = 0;
                                    dbTag.ParentTagID = 0;

                                    photoEntities.AddToTags(dbTag);
                                    tagCollection.Add(dbTag);
                                }
                                    dbPhoto.Tags.Add(dbTag);
                                

                                Tag collectionTag = new Tag();
                                collectionTag.TagName = s;
                                collectionTag.TagType = 0;
                                collectionTag.ParentTagID = 0;
                                photo.Tags.Add(collectionTag);
                            }
                        }
                        photoEntities.SaveChanges();
                        OnTagsChanged(EventArgs.Empty); 
           
                    }
                    break;

                case "Title":
                    string title = (value as string);
                    photo.Title = title;
                    dbPhoto.Title = title;

                    photoEntities.SaveChanges();
                    break;
                case "Subject":
                    string subject = (value as string);
                    photo.Subject = subject;
                    dbPhoto.Subject = subject;

                    photoEntities.SaveChanges();
                    break;
                case "Comment":
                    string comment = (value as string);
                    photo.Comment = comment;
                    dbPhoto.Comment = comment;

                    photoEntities.SaveChanges();
                    break;
                case "Rating":
                    photo.Rating = (short)value;
                    dbPhoto.Rating = (short)value;

                    photoEntities.SaveChanges();
                    break;
            }
        }

        private void ChangeValue(BitmapMetadata meta, String element, Object value)
        {
            switch (element)
            {
                case "Tag":

                    string[] split;
                    string[] stringSeparators = new string[] { ", " };
                    List<string> tagList;

                    if (meta.Keywords != null)
                    {
                        tagList = new List<string>(meta.Keywords);
                    }
                    else
                    {
                        tagList = new List<string>();
                    }
                    split = (value as String).Split(stringSeparators, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string s in split)
                    {
                        if (tagList.BinarySearch(s) < 0)
                        {
                            tagList.Add(s);
                        }
                    }

                    String val = "";
                    try
                    {

                        foreach (string tag in tagList)
                        {
                            val = val + tag + ", ";
                        }
                    }
                    catch (Exception ex)
                    {
                        Trace.WriteLine("Error creating Tag string: " + val + " Error: " + ex.Message);
                    }


                    meta.Keywords = new ReadOnlyCollection<string>(tagList);

                    break;
                case "Title":
                    meta.Title = (value as string);
                    break;
                case "Subject":
                    meta.Subject = (value as string);
                    break;
                case "Comment":
                    meta.Comment = (value as string);
                    break;
                case "Rating":
                    meta.Rating = (short)value;
                    break;
            }
        }



        private Boolean EditInplace(String path, String element, Object value)
        {
            Boolean success = false;
           
            Stream file = File.Open(path, FileMode.Open, FileAccess.ReadWrite);
            try
            {
            BitmapDecoder output = BitmapDecoder.Create(file, BitmapCreateOptions.None, BitmapCacheOption.Default);
            InPlaceBitmapMetadataWriter metadata = output.Frames[0].CreateInPlaceBitmapMetadataWriter();

            ChangeValue(metadata, element, value);
            string s = "Padding First: " + metadata.GetQuery("/app1/ifd/PaddingSchema:Padding") +
             " Padding Second: " + metadata.GetQuery("/app1/ifd/exif/PaddingSchema:Padding") +
             " Padding Third: " + metadata.GetQuery("/xmp/PaddingSchema:Padding");
            Trace.WriteLine("Padding First: " + metadata.GetQuery("/app1/ifd/PaddingSchema:Padding") +
             " Padding Second: " + metadata.GetQuery("/app1/ifd/exif/PaddingSchema:Padding") +
             " Padding Third: " + metadata.GetQuery("/xmp/PaddingSchema:Padding"));
             
           
                if (metadata.TrySave())
                {
                    success = true;
                }
                else
                {
                    success = false;
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.Message);
            }
            file.Close();
            return success;
        }



        private  void EditFullCopy(string filename, String element, Object value)
{
    // set up the creation options for the file
    BitmapCreateOptions createOptions = BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile;
 
    // padding amount, using 2Kb.  don't need much here; metadata is rather small
    uint paddingAmount = 2048;
 
    // open image file to read
    using (Stream file = File.Open(filename, FileMode.Open, FileAccess.Read))
    {
        // create the decoder for the original file.  The BitmapCreateOptions and BitmapCacheOption denote
        // a lossless transocde.  We want to preserve the pixels and cache it on load.  Otherwise, we will lose
        // quality or even not have the file ready when we save, resulting in 0b of data written
        BitmapDecoder original = BitmapDecoder.Create(file, createOptions, BitmapCacheOption.OnLoad);
        // create an encoder for the output file
        JpegBitmapEncoder output = new JpegBitmapEncoder();
 
        // add padding and tags to the file, as well as clone the data to another object
        if (original.Frames[0] != null && original.Frames[0].Metadata != null)
        {
            // Because the file is in use, the BitmapMetadata object is frozen.
            // So, we clone the object and add in the padding.
            BitmapMetadata metadata = original.Frames[0].Metadata.Clone() as BitmapMetadata;
 
            // we use the same method described in AddTags() as saving tags to save an amount of padding
            metadata.SetQuery("/app1/ifd/PaddingSchema:Padding", paddingAmount);
            metadata.SetQuery("/app1/ifd/exif/PaddingSchema:Padding", paddingAmount);
            metadata.SetQuery("/xmp/PaddingSchema:Padding", paddingAmount);
            // we add the tags we want as well.  Again, using the same method described above
            ChangeValue(metadata, element, value);

            // finally, we create a new frame that has all of this new metadata, along with the data that was in the original message
            output.Frames.Add(BitmapFrame.Create(original.Frames[0], original.Frames[0].Thumbnail, metadata, original.Frames[0].ColorContexts));
            file.Close();  // close the file to ready for overwrite
        }
        // finally, save the new file over the old file
        using (Stream outputFile = File.Open(filename, FileMode.Create, FileAccess.Write))
        {
            output.Save(outputFile);
        }
    }
}


        /*

        private void EditFullCopy(String path, String element, Object value)
        {

            Stream file = File.Open(path, FileMode.Open, FileAccess.ReadWrite);
            BitmapCreateOptions createOptions = BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile;

            BitmapDecoder original = BitmapDecoder.Create(file, createOptions, BitmapCacheOption.Default); //BitmapCacheOption.None);

            if (!original.CodecInfo.FileExtensions.Contains("jpg"))
            {
                Trace.WriteLine("The file you passed in is not a JPEG.");
                return;
            }

            JpegBitmapEncoder output = new JpegBitmapEncoder();
            BitmapMetadata metadata = original.Frames[0].Metadata.Clone() as BitmapMetadata;

            metadata.SetQuery("/app1/ifd/PaddingSchema:Padding", paddingAmount);
            metadata.SetQuery("/app1/ifd/exif/PaddingSchema:Padding", paddingAmount);
            metadata.SetQuery("/xmp/PaddingSchema:Padding", paddingAmount);

            ChangeValue(metadata, element, value);

            BitmapFrame bFrame = BitmapFrame.Create(original.Frames[0], original.Frames[0].Thumbnail, metadata, original.Frames[0].ColorContexts);

            output.Frames.Add(bFrame);

            file.Close();
            using (Stream outputFile = File.Open(path, FileMode.Create, FileAccess.Write))
            {

                output.Save(outputFile);
            }

           

        }

         */ 
          
         
        public void Edit(int id, String element, Object value)
        {
            Boolean inPlaceSuccess;

            Photo photo = photoCollection.FirstOrDefault(p => p.PhotoID == id);

            //Photo photo = photoCollection.FirstOrDefault(p => p.PhotoID == id);
            Photo dbPhoto = photoEntities.Photos.Include("Tags").FirstOrDefault(p => p.PhotoID == id);

            ChangeDBValue(photo, dbPhoto, element, value);

            inPlaceSuccess = EditInplace(photo.FullFilename, element, value);

            if (!inPlaceSuccess)
            {
                Trace.WriteLine("Not enough MetaData free space... expanding");
                EditFullCopy(photo.FullFilename, element, value);
               /* string tempFile = System.IO.Path.GetDirectoryName(photo.FullFilename) + "\\temp.jpg";
                if ((File.Exists(photo.FullFilename)) && File.Exists(tempFile))
                {
                    FileInfo origFile = new FileInfo(photo.FullFilename);
                    FileInfo newFile = new FileInfo(tempFile);
                    if (newFile.Length >= origFile.Length)
                    {
                        File.Delete(photo.FullFilename);
                        File.Move(System.IO.Path.GetDirectoryName(photo.FullFilename) + "\\temp.jpg", photo.FullFilename);
                    }
                    else
                    {
                        Trace.WriteLine("Error, New file is smaller than the Orig file");
                    }
                }
                else
                {
                    Trace.WriteLine("Error, either the file doesn't exist or Temp file didn't save correctly");


                }*/
            }



        }



        public void DoWork()
        {



            MetaDataWorkItem workItem;


            while (true)
            {
                if (work.Count > 0)
                {
                    while (work.Count > 0)
                    {
                        lock (work)
                        {
                            workItem = work.Dequeue();
                        }
                        Trace.WriteLine("Path: " + workItem.ID + " MetaData: " + workItem.MetaData + " Value: " + workItem.Value.ToString());

                        Edit(workItem.ID, workItem.MetaData, workItem.Value);

                        Thread.Sleep(10);
                    }
                    /*        NoArgDelegate photoUpdater = new NoArgDelegate(parent.UpdatePhotos);
                            parent.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, photoUpdater);
                            NoArgDelegate selectTreeUpdater = new NoArgDelegate(parent.UpdateSelectTree);
                            parent.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, selectTreeUpdater);
            */
                }

                Thread.Sleep(1000);
            }
        }

        public void AddWork(int id, string metadata, object value)
        {

            MetaDataWorkItem tempWork = new MetaDataWorkItem(id, metadata, value);
            lock (work)
            {
                work.Enqueue(tempWork);
            }
        }


        public MetaDataWorker(MainWindow mWindow)
        {

            parent = mWindow;
            // tagCCollection = parent.tagCCollection;
            tagCollection = parent.tagCollection;
            //photoCCollection = parent.photoCCollection;
            photoCollection = parent.photoCollection;
            photoEntities = new PhotoDBEntities(); // parent.photoEntities;

            //dataSet = mWindow.sqlDataSet.dataSet;
            //MakeDataView();
            // conn = new SqlCeConnection(c);
            // sqlDataSet = new SqlDataSet(conn);
            work = new Queue<MetaDataWorkItem>();

        }

    }
}
